#!/usr/bin/env python3
# coding: utf-8
"""
2025-11-05 客户表编辑界面
"""

from PySide6.QtCore import Slot, QSize
from PySide6.QtGui import QAction, QKeySequence,QIntValidator, QTextCursor, QDoubleValidator   
from PySide6.QtWidgets import QMainWindow, QApplication, QVBoxLayout, QHBoxLayout, QPushButton, QWidget,QLineEdit, QMessageBox, QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView, QLabel,QCheckBox,QTextEdit,QGroupBox,QComboBox,QDateTimeEdit,QTimeEdit,QGridLayout,QTabWidget,QScrollArea
from PySide6.QtCore import Qt, QModelIndex,QMetaObject,Q_ARG,QDateTime,QTime
from PySide6.QtGui import QGuiApplication, QCloseEvent
## from PySide6.QtCore.Qt import 
# from top.wardyan.core.widges.controlls import WTextEdit as QLineEdit

##from PySide6.QtCore import WindowState

## from top.wardyan.core.utils import winformutils
from top.wardyan.core.action.baseaction import WMainWindow,WQdialog
from top.wardyan.core.vo.vos import *
from top.wardyan.core.widges.tables import WTableView

##from warningmain import *
from top.wardyan.core.wdb.core.DBPool import POOL

from top.wardyan.core.vo.vos import *
from top.wardyan.core.wdb.core.db import *
from config import Config
from top.wardyan.core.sqlite import sqliteconfigs

import threading
import time
import re
import sys

from apscheduler.schedulers.background import BackgroundScheduler
from top.wardyan.core.utils.common import *
from top.wardyan.core.wdb.core.dic import *
import uuid

from top.wardyan.projects.checktool_manager.main.checktool_manager_const import *


SQL_FIND = "SELECT * FROM yt_customer WHERE sID = %s"
SQL_FIND_PLANS = "SELECT sID, xName FROM yt_checkplan ORDER BY sID"
SQL_FIND_USERS = "SELECT sID, xName FROM yt_user ORDER BY sID"
SQL_FIND_SELECTED_USERS = "SELECT rUser_ID FROM yt_customer_user WHERE mID = %s"

MYCONFIG = Config()

class ChecktoolCustomerEditShell (WQdialog) :
    def __init__(self, customerid) -> None:
        ##global cn
        super().__init__("ChecktoolCustomerEditShell")
        self.editable = False
        self.customerid = customerid
        self.table : WTableView = None  # type: ignore
        self.user_items = []  # 存储所有可选的用户
        self.selected_users = set()  # 存储已选择的用户ID
        self.user_widgets = {}  # 存储用户对应的复选框
        self.main()
        ## self.refreshTable()
        self.load()
        # 初始化后更新用户复选框的可编辑状态
        self.update_user_editable_state()
        
    def button_click_add(self) :
        pass

    def handle_bind(self, isDeBind : bool = False):
        if isDeBind :
            try :
                self.sCodeLineEdit.textChanged.disconnect(self.on_input_changed)
                self.xNameLineEdit.textChanged.disconnect(self.on_input_changed)
            except BaseException as e :
                pass
        else : 
            self.sCodeLineEdit.textChanged.connect(self.on_input_changed)
            self.xNameLineEdit.textChanged.connect(self.on_input_changed)
            
    def on_input_changed(self):
        self.btnSave.setEnabled(True)
        self.btnEdit.setEnabled(False)
        self.btnAudit.setEnabled(False)
        self.btnAdd.setEnabled(False)
        self.btnDel.setEnabled(False)

    def on_input_inited(self) :
        if self.customerid is not None and self.customerid > 0 :
            self.btnSave.setEnabled(False)
            self.btnEdit.setEnabled(True)
            self.btnAudit.setEnabled(True)
            self.btnAdd.setEnabled(True)
            self.btnDel.setEnabled(False)
        else :
            self.btnSave.setEnabled(True)
            self.btnEdit.setEnabled(False)
            self.btnAudit.setEnabled(False)
            self.btnAdd.setEnabled(False)
            self.btnDel.setEnabled(False)

        self.sCodeLineEdit.textChanged.connect(self.on_input_changed)
        self.xNameLineEdit.textChanged.connect(self.on_input_changed)

    def _add_form_row_two(self, layout: QGridLayout, label1_text: str, widget1: QWidget,
                        label2_text: str, widget2: QWidget, label_width: int):
        row = layout.rowCount()

        label1 = QLabel(label1_text)
        label1.setFixedWidth(label_width)
        layout.addWidget(label1, row, 0)
        layout.addWidget(widget1, row, 1)

        label2 = QLabel(label2_text)
        label2.setFixedWidth(label_width)
        layout.addWidget(label2, row, 2)
        layout.addWidget(widget2, row, 3)

    def _add_form_row_two_no_label(self, layout: QGridLayout, widget1: QWidget,
                                 widget2: QWidget):
        row = layout.rowCount()
        # 添加一些空白标签以保持布局一致
        empty_label1 = QLabel("")
        layout.addWidget(empty_label1, row, 0)
        layout.addWidget(widget1, row, 1)

        empty_label2 = QLabel("")
        layout.addWidget(empty_label2, row, 2)
        layout.addWidget(widget2, row, 3)

    def main(self) :
        HEIGHT = 40
        layout : QVBoxLayout = QVBoxLayout()
        layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignVCenter)
        self.setLayout(layout)
        
        ## 按钮: 搜索 新增
        topwidget : QWidget = QWidget()
        ##topwidget.setFixedHeight(100)
        layout.addWidget(topwidget)
        topwidget.setFixedHeight(50)
        
        # 设置背景颜色
        #topwidget.setStyleSheet("background-color: #ffff00;padding:0;margin:0;")  # 你可以将 #f0f0f0 替换为你想要的颜色代码

        toplayout : QHBoxLayout = QHBoxLayout()
        toplayout.setAlignment(Qt.AlignmentFlag.AlignRight)
        ##self.setLayout(layout)
        ##layout.addWidget(toplayout)
        topwidget.setLayout(toplayout)

        self.btnDel : QPushButton = QPushButton("删除")
        self.btnDel.setFixedHeight(HEIGHT)
        self.btnDel.setFixedWidth(150)
        self.btnDel.clicked.connect(self.btn_click_del_data)
        toplayout.addWidget(self.btnDel)

        self.btnEdit : QPushButton = QPushButton("编辑")
        self.btnEdit.setFixedHeight(HEIGHT)
        self.btnEdit.setFixedWidth(150)
        self.btnEdit.clicked.connect(self.btn_click_edit_data)
        toplayout.addWidget(self.btnEdit)

        ### 
        self.btnSave : QPushButton = QPushButton("保存")
        self.btnSave.setFixedHeight(HEIGHT)
        self.btnSave.setFixedWidth(150)
        self.btnSave.clicked.connect(self.btn_click_save_data)
        self.btnSave.setDefault(True)
        toplayout.addWidget(self.btnSave)

        self.btnAudit : QPushButton = QPushButton("审核")
        self.btnAudit.setFixedHeight(HEIGHT)
        self.btnAudit.setFixedWidth(150)
        self.btnAudit.clicked.connect(self.btn_click_audit_data)
        toplayout.addWidget(self.btnAudit)

        self.btnAdd : QPushButton = QPushButton("新增")
        self.btnAdd.setFixedHeight(HEIGHT)
        self.btnAdd.setFixedWidth(150)
        self.btnAdd.clicked.connect(self.button_click_add)
        self.btnAdd.setVisible(False)
        toplayout.addWidget(self.btnAdd)

        ###################

        ctrlw : QWidget = QWidget()
        ctrllayout : QHBoxLayout = QHBoxLayout()
        ctrlw.setLayout(ctrllayout)
        layout.addWidget(ctrlw)

        ctrllayout.setAlignment(Qt.AlignmentFlag.AlignRight)
        
        # 获取已有的 QVBoxLayout 主布局
        main_layout = self.layout()  # type: ignore

        # 创建一个 QTabWidget 作为主布局
        self.tabWidget = QTabWidget()
        main_layout.insertWidget(1, self.tabWidget)  # type: ignore

        # 创建基本信息标签页
        self.create_basic_info_tab()

        # 创建用户选择标签页
        self.create_user_selection_tab()

    def create_basic_info_tab(self):
        """
        创建基本信息标签页
        """
        # 创建一个 QWidget 作为基本信息标签页的内容
        basic_info_widget = QWidget()
        basic_info_layout = QVBoxLayout()
        basic_info_widget.setLayout(basic_info_layout)
        
        # 创建一个 QGroupBox 容器来包含表单内容
        form_group_box = QGroupBox("客户基本信息")
        form_grid_layout = QGridLayout()
        form_grid_layout.setColumnStretch(0, 1)  # 标签列比例
        form_grid_layout.setColumnStretch(1, 3)  # 控件列比例
        form_grid_layout.setColumnStretch(2, 1)  # 标签列比例
        form_grid_layout.setColumnStretch(3, 3)  # 控件列比例

        label_width = 120  # 所有标签统一宽度

        # s开头的字段不需要显示，但需要创建以便在load_data中使用
        # sID 输入框
        self.sIDLineEdit = QLineEdit()
        self.sIDLineEdit.setMaxLength(100)
        self.sIDLineEdit.setReadOnly(True)
        self.sIDLineEdit.setFixedHeight(40)
        self.sIDLineEdit.setVisible(False)  # 隐藏s开头的字段

        # sCode 输入框
        self.sCodeLineEdit = QLineEdit()
        self.sCodeLineEdit.setMaxLength(100)
        self.sCodeLineEdit.setFixedHeight(40)

        # xName 输入框
        self.xNameLineEdit = QLineEdit()
        self.xNameLineEdit.setMaxLength(100)
        self.xNameLineEdit.setFixedHeight(40)

        # 一行显示两个编辑框: 客户代码 + 名称
        self._add_form_row_two(form_grid_layout, "客户代码:", self.sCodeLineEdit,
                              "名称:", self.xNameLineEdit, label_width)

        # xIsHB 勾选框 (不显示标签)
        self.xIsHBCheckBox = QCheckBox("启用心跳")
        self.xIsHBCheckBox.setFixedHeight(40)

        # xIsCheck 勾选框 (不显示标签)
        self.xIsCheckCheckBox = QCheckBox("启用检查")
        self.xIsCheckCheckBox.setFixedHeight(40)

        # 一行显示两个编辑框: 启用心跳 + 启用检查 (不显示标签)
        self._add_form_row_two_no_label(form_grid_layout, self.xIsHBCheckBox, self.xIsCheckCheckBox)

        # xHBIntv 输入框
        self.xHBIntvLineEdit = QLineEdit()
        # 限制只能输入整数，并设置范围
        hb_intv_validator = QIntValidator()
        hb_intv_validator.setRange(0, 2147483647)  # 整型最大值
        self.xHBIntvLineEdit.setValidator(hb_intv_validator)
        self.xHBIntvLineEdit.setFixedHeight(40)

        # xHBOTTime 输入框
        self.xHBOTTimeLineEdit = QLineEdit()
        # 限制只能输入整数，并设置范围
        hbot_validator = QIntValidator()
        hbot_validator.setRange(0, 2147483647)  # 整型最大值
        self.xHBOTTimeLineEdit.setValidator(hbot_validator)
        self.xHBOTTimeLineEdit.setFixedHeight(40)

        # 一行显示两个编辑框: 心跳间隔毫秒 + 心跳超时毫秒
        self._add_form_row_two(form_grid_layout, "心跳间隔毫秒:", self.xHBIntvLineEdit,
                              "心跳超时毫秒:", self.xHBOTTimeLineEdit, label_width)

        # rPlan_ID 下拉框
        self.rPlanIDComboBox = QComboBox()
        self.rPlanIDComboBox.setFixedHeight(40)
        # 先添加空选项和0选项
        self.rPlanIDComboBox.addItem("无", 0)
        # 后续从数据库加载数据检查方案

        # 一行显示两个编辑框: 数据检查方案 (单独一行)
        self._add_form_row_two(form_grid_layout, "数据检查方案:", self.rPlanIDComboBox,
                              "", QWidget(), label_width)

        # xLongitude 输入框
        self.xLongitudeLineEdit = QLineEdit()
        # 限制只能输入浮点数，并设置经纬度范围
        longitude_validator = QDoubleValidator()
        longitude_validator.setRange(-180.0, 180.0, 10)  # 经度范围
        self.xLongitudeLineEdit.setValidator(longitude_validator)
        self.xLongitudeLineEdit.setFixedHeight(40)

        # xLatitude 输入框
        self.xLatitudeLineEdit = QLineEdit()
        # 限制只能输入浮点数，并设置经纬度范围
        latitude_validator = QDoubleValidator()
        latitude_validator.setRange(-90.0, 90.0, 10)  # 纬度范围
        self.xLatitudeLineEdit.setValidator(latitude_validator)
        self.xLatitudeLineEdit.setFixedHeight(40)

        # 一行显示两个编辑框: 经度 + 纬度
        self._add_form_row_two(form_grid_layout, "经度:", self.xLongitudeLineEdit,
                              "纬度:", self.xLatitudeLineEdit, label_width)

        # 创建可编辑时间字段区域
        time_edit_group_box = QGroupBox("可编辑时间设置")
        time_edit_layout = QGridLayout()
        time_edit_layout.setColumnStretch(0, 1)
        time_edit_layout.setColumnStretch(1, 3)
        time_edit_layout.setColumnStretch(2, 1)
        time_edit_layout.setColumnStretch(3, 3)
        
        # xScheduleBeginTime 时间选择 (可编辑)
        self.xScheduleBeginTimeEdit = QTimeEdit()
        self.xScheduleBeginTimeEdit.setDisplayFormat("HH:mm:ss")
        self.xScheduleBeginTimeEdit.setFixedHeight(40)
        # xScheduleEndTime 时间选择 (可编辑)
        self.xScheduleEndTimeEdit = QTimeEdit()
        self.xScheduleEndTimeEdit.setDisplayFormat("HH:mm:ss")
        self.xScheduleEndTimeEdit.setFixedHeight(40)
        # 一行显示两个编辑框: 定时任务开始 + 定时任务结束
        self._add_form_row_two(time_edit_layout, "定时任务开始:", self.xScheduleBeginTimeEdit,
                              "定时任务结束:", self.xScheduleEndTimeEdit, label_width)
        
        time_edit_group_box.setLayout(time_edit_layout)
        basic_info_layout.addWidget(time_edit_group_box)  # type: ignore

        # 创建只读日期时间字段区域
        datetime_readonly_group_box = QGroupBox("系统时间信息(只读)")
        datetime_readonly_layout = QGridLayout()
        datetime_readonly_layout.setColumnStretch(0, 1)
        datetime_readonly_layout.setColumnStretch(1, 3)
        datetime_readonly_layout.setColumnStretch(2, 1)
        datetime_readonly_layout.setColumnStretch(3, 3)
        
        # xLastHBTime 日期时间选择 (只读)
        self.xLastHBTimeEdit = QDateTimeEdit()
        self.xLastHBTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xLastHBTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xLastHBTimeEdit.setFixedHeight(40)
        self.xLastHBTimeEdit.setReadOnly(True)  # 只读

        # xNextHBTime 日期时间选择 (只读)
        self.xNextHBTimeEdit = QDateTimeEdit()
        self.xNextHBTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextHBTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextHBTimeEdit.setFixedHeight(40)
        self.xNextHBTimeEdit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 最后心跳 + 下次心跳
        self._add_form_row_two(datetime_readonly_layout, "最后心跳:", self.xLastHBTimeEdit,
                              "下次心跳:", self.xNextHBTimeEdit, label_width)

        # xNextHBOTTime 日期时间选择 (只读)
        self.xNextHBOTTimeEdit = QDateTimeEdit()
        self.xNextHBOTTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextHBOTTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextHBOTTimeEdit.setFixedHeight(40)
        self.xNextHBOTTimeEdit.setReadOnly(True)  # 只读

        # xLastCmdTime 日期时间选择 (只读)
        self.xLastCmdTimeEdit = QDateTimeEdit()
        self.xLastCmdTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xLastCmdTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xLastCmdTimeEdit.setFixedHeight(40)
        self.xLastCmdTimeEdit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 心跳超时 + 最后命令
        self._add_form_row_two(datetime_readonly_layout, "心跳超时:", self.xNextHBOTTimeEdit,
                              "最后命令:", self.xLastCmdTimeEdit, label_width)

        # xNextCmdTime 日期时间选择 (只读)
        self.xNextCmdTimeEdit = QDateTimeEdit()
        self.xNextCmdTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextCmdTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextCmdTimeEdit.setFixedHeight(40)
        self.xNextCmdTimeEdit.setReadOnly(True)  # 只读

        # xClientVersion 日期时间选择 (只读)
        self.xClientVersionEdit = QDateTimeEdit()
        self.xClientVersionEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xClientVersionEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xClientVersionEdit.setFixedHeight(40)
        self.xClientVersionEdit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 下次命令 + 客户端版本
        self._add_form_row_two(datetime_readonly_layout, "下次命令:", self.xNextCmdTimeEdit,
                              "客户端版本:", self.xClientVersionEdit, label_width)

        # xERPVersion 日期时间选择 (只读)
        self.xERPVersionEdit = QDateTimeEdit()
        self.xERPVersionEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xERPVersionEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xERPVersionEdit.setFixedHeight(40)
        self.xERPVersionEdit.setReadOnly(True)  # 只读

        # xNextDailyTime 日期时间选择 (只读)
        self.xNextDailyTimeEdit = QDateTimeEdit()
        self.xNextDailyTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDailyTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDailyTimeEdit.setFixedHeight(40)
        self.xNextDailyTimeEdit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: ERP版本 + 下次日任务
        self._add_form_row_two(datetime_readonly_layout, "ERP版本:", self.xERPVersionEdit,
                              "下次日任务:", self.xNextDailyTimeEdit, label_width)

        # xDatetime2 日期时间选择 (只读)
        self.xDatetime2Edit = QDateTimeEdit()
        self.xDatetime2Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDatetime2Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDatetime2Edit.setFixedHeight(40)
        self.xDatetime2Edit.setReadOnly(True)  # 只读

        # xNextDatetime2 日期时间选择 (只读)
        self.xNextDatetime2Edit = QDateTimeEdit()
        self.xNextDatetime2Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDatetime2Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDatetime2Edit.setFixedHeight(40)
        self.xNextDatetime2Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 上次月度任务 + 下次月度任务
        self._add_form_row_two(datetime_readonly_layout, "上次月度任务:", self.xDatetime2Edit,
                              "下次月度任务:", self.xNextDatetime2Edit, label_width)

        # xDatetime3 日期时间选择 (只读)
        self.xDatetime3Edit = QDateTimeEdit()
        self.xDatetime3Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDatetime3Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDatetime3Edit.setFixedHeight(40)
        self.xDatetime3Edit.setReadOnly(True)  # 只读

        # xNextDatetime3 日期时间选择 (只读)
        self.xNextDatetime3Edit = QDateTimeEdit()
        self.xNextDatetime3Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDatetime3Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDatetime3Edit.setFixedHeight(40)
        self.xNextDatetime3Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 上次周任务 + 下次周任务
        self._add_form_row_two(datetime_readonly_layout, "上次周任务:", self.xDatetime3Edit,
                              "下次周任务:", self.xNextDatetime3Edit, label_width)

        # xDatetime5 日期时间选择 (只读)
        self.xDatetime5Edit = QDateTimeEdit()
        self.xDatetime5Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDatetime5Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDatetime5Edit.setFixedHeight(40)
        self.xDatetime5Edit.setReadOnly(True)  # 只读

        # xNextDatetime5 日期时间选择 (只读)
        self.xNextDatetime5Edit = QDateTimeEdit()
        self.xNextDatetime5Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDatetime5Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDatetime5Edit.setFixedHeight(40)
        self.xNextDatetime5Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 上次小时任务 + 下次小时任务
        self._add_form_row_two(datetime_readonly_layout, "上次小时任务:", self.xDatetime5Edit,
                              "下次小时任务:", self.xNextDatetime5Edit, label_width)

        # xDatetime6 日期时间选择 (只读)
        self.xDatetime6Edit = QDateTimeEdit()
        self.xDatetime6Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDatetime6Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDatetime6Edit.setFixedHeight(40)
        self.xDatetime6Edit.setReadOnly(True)  # 只读

        # xNextDatetime6 日期时间选择 (只读)
        self.xNextDatetime6Edit = QDateTimeEdit()
        self.xNextDatetime6Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDatetime6Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDatetime6Edit.setFixedHeight(40)
        self.xNextDatetime6Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 上次十分钟任务 + 下次十分钟任务
        self._add_form_row_two(datetime_readonly_layout, "上次十分钟任务:", self.xDatetime6Edit,
                              "下次十分钟任务:", self.xNextDatetime6Edit, label_width)

        # xDatetime7 日期时间选择 (只读)
        self.xDatetime7Edit = QDateTimeEdit()
        self.xDatetime7Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDatetime7Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDatetime7Edit.setFixedHeight(40)
        self.xDatetime7Edit.setReadOnly(True)  # 只读

        # xNextDatetime7 日期时间选择 (只读)
        self.xNextDatetime7Edit = QDateTimeEdit()
        self.xNextDatetime7Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDatetime7Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDatetime7Edit.setFixedHeight(40)
        self.xNextDatetime7Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 上次五分钟任务 + 下次五分钟任务
        self._add_form_row_two(datetime_readonly_layout, "上次五分钟任务:", self.xDatetime7Edit,
                              "下次五分钟任务:", self.xNextDatetime7Edit, label_width)

        # xIsDef 勾选框 (不显示标签)
        self.xIsDefCheckBox = QCheckBox("默认客户")
        self.xIsDefCheckBox.setFixedHeight(40)

        # xDailyTime 日期时间选择 (只读)
        self.xDailyTimeEdit = QDateTimeEdit()
        self.xDailyTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDailyTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDailyTimeEdit.setFixedHeight(40)
        self.xDailyTimeEdit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 默认客户 + 日任务时间 (不显示标签)
        self._add_form_row_two_no_label(form_grid_layout, self.xIsDefCheckBox, self.xDailyTimeEdit)

        # xNextHBOFTimeAlarm 日期时间选择 (只读)
        self.xNextHBOFTimeAlarmEdit = QDateTimeEdit()
        self.xNextHBOFTimeAlarmEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextHBOFTimeAlarmEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextHBOFTimeAlarmEdit.setFixedHeight(40)
        self.xNextHBOFTimeAlarmEdit.setReadOnly(True)  # 只读

        # xDatetime10 日期时间选择 (只读)
        self.xDatetime10Edit = QDateTimeEdit()
        self.xDatetime10Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDatetime10Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDatetime10Edit.setFixedHeight(40)
        self.xDatetime10Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 心跳超时报警 + Datetime10
        self._add_form_row_two(datetime_readonly_layout, "心跳超时报警:", self.xNextHBOFTimeAlarmEdit,
                              "Datetime10:", self.xDatetime10Edit, label_width)

        # xNextDatetime10 日期时间选择 (只读)
        self.xNextDatetime10Edit = QDateTimeEdit()
        self.xNextDatetime10Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDatetime10Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDatetime10Edit.setFixedHeight(40)
        self.xNextDatetime10Edit.setReadOnly(True)  # 只读

        # xDatetime11 日期时间选择 (只读)
        self.xDatetime11Edit = QDateTimeEdit()
        self.xDatetime11Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xDatetime11Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xDatetime11Edit.setFixedHeight(40)
        self.xDatetime11Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 下次Datetime10 + Datetime11
        self._add_form_row_two(datetime_readonly_layout, "下次Datetime10:", self.xNextDatetime10Edit,
                              "Datetime11:", self.xDatetime11Edit, label_width)

        # xNextDatetime11 日期时间选择 (只读)
        self.xNextDatetime11Edit = QDateTimeEdit()
        self.xNextDatetime11Edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.xNextDatetime11Edit.setCalendarPopup(True)  # 启用下拉日历选择
        self.xNextDatetime11Edit.setFixedHeight(40)
        self.xNextDatetime11Edit.setReadOnly(True)  # 只读

        # 一行显示两个编辑框: 下次Datetime11 (单独一行)
        self._add_form_row_two(datetime_readonly_layout, "下次Datetime11:", self.xNextDatetime11Edit,
                              "", QWidget(), label_width)

        # xKey 输入框 (隐藏字段)
        self.xKeyLineEdit = QLineEdit()
        self.xKeyLineEdit.setMaxLength(100)
        self.xKeyLineEdit.setFixedHeight(40)
        self.xKeyLineEdit.setVisible(False)  # 隐藏字段

        # sStatus 输入框 (隐藏字段)
        self.sStatusLineEdit = QLineEdit()
        self.sStatusLineEdit.setValidator(QIntValidator())  # 限制只能输入整数
        self.sStatusLineEdit.setFixedHeight(40)
        self.sStatusLineEdit.setVisible(False)  # 隐藏字段

        # sVersion 输入框 (隐藏字段)
        self.sVersionLineEdit = QLineEdit()
        self.sVersionLineEdit.setValidator(QIntValidator())  # 限制只能输入整数
        self.sVersionLineEdit.setFixedHeight(40)
        self.sVersionLineEdit.setVisible(False)  # 隐藏字段

        # sCreateTime 日期时间选择 (隐藏字段)
        self.sCreateTimeEdit = QDateTimeEdit()
        self.sCreateTimeEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.sCreateTimeEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.sCreateTimeEdit.setFixedHeight(40)
        self.sCreateTimeEdit.setVisible(False)  # 隐藏字段

        # sLastUpdate 日期时间选择 (隐藏字段)
        self.sLastUpdateEdit = QDateTimeEdit()
        self.sLastUpdateEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.sLastUpdateEdit.setCalendarPopup(True)  # 启用下拉日历选择
        self.sLastUpdateEdit.setFixedHeight(40)
        self.sLastUpdateEdit.setVisible(False)  # 隐藏字段

        form_group_box.setLayout(form_grid_layout)
        basic_info_layout.addWidget(form_group_box)
        
        # 将基本信息标签页添加到标签控件中
        self.tabWidget.addTab(basic_info_widget, "基本信息")

    def create_user_selection_tab(self):
        """
        创建用户选择标签页
        """
        # 创建一个 QWidget 作为用户选择标签页的内容
        user_selection_widget = QWidget()
        user_selection_layout = QVBoxLayout()
        user_selection_widget.setLayout(user_selection_layout)
        
        # 创建用户选择区域
        user_group_box = QGroupBox("用户选择")
        user_layout = QVBoxLayout()
        user_group_box.setLayout(user_layout)
        user_selection_layout.addWidget(user_group_box)
        
        # 创建一个滚动区域来容纳用户选择
        self.userScrollArea = QScrollArea()
        self.userScrollArea.setWidgetResizable(True)  # 允许滚动区域自动调整大小
        self.userScrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)  # 禁用水平滚动条
        self.userScrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)  # 需要时显示垂直滚动条
        
        self.userWidget = QWidget()
        self.userLayout = QVBoxLayout()
        self.userWidget.setLayout(self.userLayout)
        self.userScrollArea.setWidget(self.userWidget)
        user_layout.addWidget(self.userScrollArea)
        
        # 将用户选择标签页添加到标签控件中
        self.tabWidget.addTab(user_selection_widget, "关联用户")

    def load_check_plans(self):
        """
        从数据库加载所有数据检查方案到界面
        """
        try:
            # 查询所有数据检查方案
            plans = query(sql=SQL_FIND_PLANS, config=MYCONFIG)
            if plans is not None:
                self.rPlanIDComboBox.clear()
                self.rPlanIDComboBox.addItem("无", 0)
                for plan in plans:
                    self.rPlanIDComboBox.addItem(plan["xName"], plan["sID"])
        except Exception as e:
            print(f"加载数据检查方案失败: {e}")

    def load_users(self):
        """
        从数据库加载所有用户到界面
        """
        try:
            # 查询所有用户
            users = query(sql=SQL_FIND_USERS, config=MYCONFIG)
            if users is not None:
                self.user_items = users
                
                # 查询当前客户已选择的用户
                if self.customerid is not None and self.customerid > 0:
                    selected_datas = query(sql=SQL_FIND_SELECTED_USERS, params=(self.customerid,), config=MYCONFIG)
                    self.selected_users = {data["rUser_ID"] for data in selected_datas} if selected_datas else set()
                
                # 创建用户复选框
                self.create_user_widgets()
        except Exception as e:
            print(f"加载用户失败: {e}")

    def load_data(self, data: dict):
        """
        加载数据到编辑界面
        :param data: 字典格式的数据，如从数据库查询得到
        """
        self.handle_bind(True)
        self.data = data
        self.sIDLineEdit.setText(str(data.get("sID", "")))
        self.sCodeLineEdit.setText(data.get("sCode", ""))
        self.xNameLineEdit.setText(data.get("xName", ""))
        # 不显示密钥字段，但仍需要加载数据
        self.xKeyLineEdit.setText(data.get("xKey", ""))
        self.xIsHBCheckBox.setChecked(bool(data.get("xIsHB", False)))
        self.xHBIntvLineEdit.setText(str(data.get("xHBIntv", "")))
        self.xHBOTTimeLineEdit.setText(str(data.get("xHBOTTime", "")))
        
        # 日期时间字段处理
        last_hb_time = data.get("xLastHBTime")
        if last_hb_time:
            self.xLastHBTimeEdit.setDateTime(QDateTime.fromString(str(last_hb_time), "yyyy-MM-dd HH:mm:ss"))
            
        next_hb_time = data.get("xNextHBTime")
        if next_hb_time:
            self.xNextHBTimeEdit.setDateTime(QDateTime.fromString(str(next_hb_time), "yyyy-MM-dd HH:mm:ss"))
            
        next_hb_ot_time = data.get("xNextHBOTTime")
        if next_hb_ot_time:
            self.xNextHBOTTimeEdit.setDateTime(QDateTime.fromString(str(next_hb_ot_time), "yyyy-MM-dd HH:mm:ss"))
            
        self.xIsCheckCheckBox.setChecked(bool(data.get("xIsCheck", False)))
        
        last_cmd_time = data.get("xLastCmdTime")
        if last_cmd_time:
            self.xLastCmdTimeEdit.setDateTime(QDateTime.fromString(str(last_cmd_time), "yyyy-MM-dd HH:mm:ss"))
            
        next_cmd_time = data.get("xNextCmdTime")
        if next_cmd_time:
            self.xNextCmdTimeEdit.setDateTime(QDateTime.fromString(str(next_cmd_time), "yyyy-MM-dd HH:mm:ss"))
            
        client_version = data.get("xClientVersion")
        if client_version:
            self.xClientVersionEdit.setDateTime(QDateTime.fromString(str(client_version), "yyyy-MM-dd HH:mm:ss"))
            
        erp_version = data.get("xERPVersion")
        if erp_version:
            self.xERPVersionEdit.setDateTime(QDateTime.fromString(str(erp_version), "yyyy-MM-dd HH:mm:ss"))
            
        self.sStatusLineEdit.setText(str(data.get("sStatus", "")))
        self.sVersionLineEdit.setText(str(data.get("sVersion", "")))
        
        create_time = data.get("sCreateTime")
        if create_time:
            self.sCreateTimeEdit.setDateTime(QDateTime.fromString(str(create_time), "yyyy-MM-dd HH:mm:ss"))
            
        last_update = data.get("sLastUpdate")
        if last_update:
            self.sLastUpdateEdit.setDateTime(QDateTime.fromString(str(last_update), "yyyy-MM-dd HH:mm:ss"))
            
        next_daily_time = data.get("xNextDailyTime")
        if next_daily_time:
            self.xNextDailyTimeEdit.setDateTime(QDateTime.fromString(str(next_daily_time), "yyyy-MM-dd HH:mm:ss"))
            
        datetime2 = data.get("xDatetime2")
        if datetime2:
            self.xDatetime2Edit.setDateTime(QDateTime.fromString(str(datetime2), "yyyy-MM-dd HH:mm:ss"))
            
        next_datetime2 = data.get("xNextDatetime2")
        if next_datetime2:
            self.xNextDatetime2Edit.setDateTime(QDateTime.fromString(str(next_datetime2), "yyyy-MM-dd HH:mm:ss"))
            
        datetime3 = data.get("xDatetime3")
        if datetime3:
            self.xDatetime3Edit.setDateTime(QDateTime.fromString(str(datetime3), "yyyy-MM-dd HH:mm:ss"))
            
        next_datetime3 = data.get("xNextDatetime3")
        if next_datetime3:
            self.xNextDatetime3Edit.setDateTime(QDateTime.fromString(str(next_datetime3), "yyyy-MM-dd HH:mm:ss"))
            
        datetime5 = data.get("xDatetime5")
        if datetime5:
            self.xDatetime5Edit.setDateTime(QDateTime.fromString(str(datetime5), "yyyy-MM-dd HH:mm:ss"))
            
        next_datetime5 = data.get("xNextDatetime5")
        if next_datetime5:
            self.xNextDatetime5Edit.setDateTime(QDateTime.fromString(str(next_datetime5), "yyyy-MM-dd HH:mm:ss"))
            
        datetime6 = data.get("xDatetime6")
        if datetime6:
            self.xDatetime6Edit.setDateTime(QDateTime.fromString(str(datetime6), "yyyy-MM-dd HH:mm:ss"))
            
        next_datetime6 = data.get("xNextDatetime6")
        if next_datetime6:
            self.xNextDatetime6Edit.setDateTime(QDateTime.fromString(str(next_datetime6), "yyyy-MM-dd HH:mm:ss"))
            
        datetime7 = data.get("xDatetime7")
        if datetime7:
            self.xDatetime7Edit.setDateTime(QDateTime.fromString(str(datetime7), "yyyy-MM-dd HH:mm:ss"))
            
        next_datetime7 = data.get("xNextDatetime7")
        if next_datetime7:
            self.xNextDatetime7Edit.setDateTime(QDateTime.fromString(str(next_datetime7), "yyyy-MM-dd HH:mm:ss"))
            
        schedule_begin_time = data.get("xScheduleBeginTime")
        if schedule_begin_time:
            self.xScheduleBeginTimeEdit.setTime(QDateTime.fromString(str(schedule_begin_time), "HH:mm:ss").time())
            
        schedule_end_time = data.get("xScheduleEndTime")
        if schedule_end_time:
            self.xScheduleEndTimeEdit.setTime(QDateTime.fromString(str(schedule_end_time), "HH:mm:ss").time())
            
        daily_time = data.get("xDailyTime")
        if daily_time:
            self.xDailyTimeEdit.setDateTime(QDateTime.fromString(str(daily_time), "yyyy-MM-dd HH:mm:ss"))
            
        next_hb_of_time_alarm = data.get("xNextHBOFTimeAlarm")
        if next_hb_of_time_alarm:
            self.xNextHBOFTimeAlarmEdit.setDateTime(QDateTime.fromString(str(next_hb_of_time_alarm), "yyyy-MM-dd HH:mm:ss"))
            
        # 不显示默认客户字段，但仍需要加载数据
        self.xIsDefCheckBox.setChecked(bool(data.get("xIsDef", False)))
        
        datetime10 = data.get("xDatetime10")
        if datetime10:
            self.xDatetime10Edit.setDateTime(QDateTime.fromString(str(datetime10), "yyyy-MM-dd HH:mm:ss"))
            
        next_datetime10 = data.get("xNextDatetime10")
        if next_datetime10:
            self.xNextDatetime10Edit.setDateTime(QDateTime.fromString(str(next_datetime10), "yyyy-MM-dd HH:mm:ss"))
            
        datetime11 = data.get("xDatetime11")
        if datetime11:
            self.xDatetime11Edit.setDateTime(QDateTime.fromString(str(datetime11), "yyyy-MM-dd HH:mm:ss"))
            
        next_datetime11 = data.get("xNextDatetime11")
        if next_datetime11:
            self.xNextDatetime11Edit.setDateTime(QDateTime.fromString(str(next_datetime11), "yyyy-MM-dd HH:mm:ss"))
            
        # 设置数据检查方案
        plan_id = data.get("rPlan_ID", 0)
        index = self.rPlanIDComboBox.findData(plan_id)
        if index >= 0:
            self.rPlanIDComboBox.setCurrentIndex(index)
            
        # 经度纬度为空时显示为0
        longitude = data.get("xLongitude", 0.0)
        latitude = data.get("xLatitude", 0.0)
        self.xLongitudeLineEdit.setText(str(longitude if longitude else 0.0))
        self.xLatitudeLineEdit.setText(str(latitude if latitude else 0.0))

        ## 设置是否可以修改
        # 以s开头的字段不允许编辑且已隐藏
        # 日期时间类型字段只显示
        # TIME类型字段只显示
        
        # 设置可编辑字段
        self.sCodeLineEdit.setReadOnly(not self.editable)
        self.xNameLineEdit.setReadOnly(not self.editable)
        # 不显示密钥字段，但仍需要设置可编辑性
        self.xKeyLineEdit.setReadOnly(not self.editable)
        self.xIsHBCheckBox.setEnabled(self.editable)
        self.xHBIntvLineEdit.setReadOnly(not self.editable)
        self.xHBOTTimeLineEdit.setReadOnly(not self.editable)
        self.xIsCheckCheckBox.setEnabled(self.editable)
        self.rPlanIDComboBox.setEnabled(self.editable)
        self.xIsDefCheckBox.setEnabled(self.editable)
        # TIME类型字段
        self.xScheduleBeginTimeEdit.setReadOnly(not self.editable)
        self.xScheduleEndTimeEdit.setReadOnly(not self.editable)
        # 经度纬度
        self.xLongitudeLineEdit.setReadOnly(not self.editable)
        self.xLatitudeLineEdit.setReadOnly(not self.editable)

        ## 判断按钮
        self.on_input_inited()
        self.handle_bind(False)

        s : int = int(data.get("sStatus", 0))
        if s > 2 :
            self.btnAudit.setText("已审核")
            self.btnAudit.setEnabled(False)
            self.btnAudit.setDisabled(True)
        else :
            self.btnAudit.setText("待审核")
            self.btnAudit.setEnabled(True)

    def load(self) :
        # 加载数据检查方案
        self.load_check_plans()
        # 加载用户列表
        self.load_users()
        
        if self.customerid is not None and self.customerid > 0 :
            datas = query(sql=SQL_FIND, params=(self.customerid,), config=MYCONFIG)
            if datas is not None and len(datas) > 0 :
                data = datas[0]
                self.load_data(data)
        else:
            # 新增界面，设置默认值
            self.set_default_values()
        self.on_input_inited()

    def clear_user_layout(self):
        """
        清空用户布局
        """
        # 删除所有子控件和子布局
        while self.userLayout.count():
            child = self.userLayout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()
            elif child.layout():
                # 递归删除子布局中的控件
                sub_layout = child.layout()
                while sub_layout.count():
                    sub_child = sub_layout.takeAt(0)
                    if sub_child.widget():
                        sub_child.widget().deleteLater()
                child.layout().deleteLater()
        self.user_widgets.clear()
        
    def create_user_widgets(self):
        """
        创建用户复选框控件
        """
        # 清空现有的用户布局
        self.clear_user_layout()
        
        # 如果没有用户，直接返回
        if not self.user_items:
            return
        
        # 每行显示2个用户
        row_layout = None
        for i, user in enumerate(self.user_items):
            # 每行两个用户
            if i % 2 == 0:
                row_layout = QHBoxLayout()
                self.userLayout.addLayout(row_layout)
            
            # 创建复选框
            user_id = user["sID"]
            user_name = user["xName"] or f"用户{user_id}"
            checkbox = QCheckBox(user_name)
            checkbox.setChecked(user_id in self.selected_users)
            
            # 连接复选框状态改变信号到输入变化处理方法
            checkbox.stateChanged.connect(self.on_user_changed)
            
            # 设置可编辑状态
            checkbox.setEnabled(self.editable)
            
            # 保存复选框引用
            self.user_widgets[user_id] = checkbox
            
            # 添加到行布局
            if row_layout:
                row_layout.addWidget(checkbox)
        
        # 添加伸展因子使布局美观
        if row_layout:
            # 先确保最后一行有伸展因子
            row_layout.addStretch()
        else:
            # 如果没有任何用户，也添加一个空的伸展因子
            self.userLayout.addStretch()
            
    def update_user_editable_state(self):
        """
        更新用户复选框的可编辑状态
        """
        for user_id, checkbox in self.user_widgets.items():
            checkbox.setEnabled(self.editable)
            
    def on_user_changed(self):
        """
        处理用户复选框状态改变事件
        """
        self.on_input_changed()

    def btn_click_edit_data(self) :
        self.editable = True
        params = (self.customerid,)
        exec(sql="UPDATE yt_customer SET sStatus = 2, sVersion = ISNULL(sVersion, 0) + 1, sLastUpdate = GETDATE() WHERE sID = %s", params=params, config=MYCONFIG)
        self.on_input_inited()
        self.load()
        self.btnEdit.setDisabled(True)
        self.btnDel.setEnabled(True)
        # 更新用户复选框的可编辑状态
        self.update_user_editable_state()

    def btn_click_del_data(self) :
        ## 先检查下能不能删
        self.editable = True
        params = (self.customerid,)
        try:
            # 先删除明细表中的记录
            exec(sql="DELETE FROM yt_customer_user WHERE mID = %s", params=params, config=MYCONFIG)
            # 再删除主表记录
            exec(sql="DELETE FROM yt_customer WHERE sID = %s", params=params, config=MYCONFIG)
            QMessageBox.information(self, "删除成功", "数据删除成功！")
            self.close()
        except Exception as e:
            QMessageBox.warning(self, "删除失败", f"删除数据时发生错误：{str(e)}")

    def btn_click_audit_data(self) :
        self.editable = False
        params = (self.customerid,)
        exec("UPDATE yt_customer SET sStatus = 5, sVersion = ISNULL(sVersion, 0) + 1, sLastUpdate = GETDATE() WHERE sID = %s", params, MYCONFIG)
        QMessageBox.information(self, "保存成功", "数据保存成功！")
        self.on_input_inited()
        self.load()
        # 更新用户复选框的可编辑状态
        self.update_user_editable_state()

    def btn_click_save_data(self) -> None:
        """
        从界面控件提取数据，返回字典格式
        """
        # 验证必填字段
        if not self.sCodeLineEdit.text().strip():
            QMessageBox.warning(self, "提示", "客户代码不能为空！")
            return
        if not self.xNameLineEdit.text().strip():
            QMessageBox.warning(self, "提示", "名称不能为空！")
            return

        # 验证文本框长度
        if len(self.sCodeLineEdit.text().strip()) > 100:
            QMessageBox.warning(self, "提示", "客户代码长度不能超过100个字符！")
            return
        if len(self.xNameLineEdit.text().strip()) > 100:
            QMessageBox.warning(self, "提示", "名称长度不能超过100个字符！")
            return

        # 验证数值输入
        hb_intv_text = self.xHBIntvLineEdit.text().strip()
        hbot_time_text = self.xHBOTTimeLineEdit.text().strip()
        
        if hb_intv_text:
            try:
                hb_intv = int(hb_intv_text)
                if hb_intv < 0 or hb_intv > 2147483647:
                    QMessageBox.warning(self, "提示", "心跳间隔毫秒必须在0到2147483647之间！")
                    return
            except ValueError:
                QMessageBox.warning(self, "提示", "心跳间隔毫秒必须是整数！")
                return
        
        if hbot_time_text:
            try:
                hbot_time = int(hbot_time_text)
                if hbot_time < 0 or hbot_time > 2147483647:
                    QMessageBox.warning(self, "提示", "心跳超时毫秒必须在0到2147483647之间！")
                    return
            except ValueError:
                QMessageBox.warning(self, "提示", "心跳超时毫秒必须是整数！")
                return

        # 验证经纬度
        longitude_text = self.xLongitudeLineEdit.text().strip()
        latitude_text = self.xLatitudeLineEdit.text().strip()
        
        if longitude_text:
            try:
                longitude = float(longitude_text)
                if longitude < -180.0 or longitude > 180.0:
                    QMessageBox.warning(self, "提示", "经度必须在-180到180之间！")
                    return
            except ValueError:
                QMessageBox.warning(self, "提示", "经度必须是数字！")
                return
        
        if latitude_text:
            try:
                latitude = float(latitude_text)
                if latitude < -90.0 or latitude > 90.0:
                    QMessageBox.warning(self, "提示", "纬度必须在-90到90之间！")
                    return
            except ValueError:
                QMessageBox.warning(self, "提示", "纬度必须是数字！")
                return

        # 获取客户代码作为密钥
        customer_code = self.sCodeLineEdit.text().strip()

        # 准备参数，确保所有数值类型正确
        s_code = customer_code
        x_name = self.xNameLineEdit.text().strip()
        x_key = customer_code  # 将客户代码作为密钥
        x_is_hb = int(self.xIsHBCheckBox.isChecked())
        x_hb_intv = int(hb_intv_text or 0)
        x_hb_ot_time = int(hbot_time_text or 0)
        x_is_check = int(self.xIsCheckCheckBox.isChecked())
        r_plan_id = self.rPlanIDComboBox.currentData() or 0
        x_is_def = int(self.xIsDefCheckBox.isChecked())
        # 经度纬度为空时显示为0
        x_longitide = float(longitude_text or 0.0)
        x_latitude = float(latitude_text or 0.0)
        # TIME类型字段
        x_schedule_begin_time = self.xScheduleBeginTimeEdit.time().toString("HH:mm:ss")
        x_schedule_end_time = self.xScheduleEndTimeEdit.time().toString("HH:mm:ss")
        
        try:
            # 根据是否有sID判断是新增还是修改
            if self.customerid is not None and self.customerid > 0 :
                # 修改
                sql = """UPDATE dbo.yt_customer 
    SET sCode = %s, xName = %s, xKey = %s, xIsHB = %s, xHBIntv = %s, xHBOTTime = %s, xIsCheck = %s, 
        rPlan_ID = %s, xIsDef = %s, xLongitude = %s, xLatitude = %s,
        xScheduleBeginTime = CAST(%s AS TIME), xScheduleEndTime = CAST(%s AS TIME),
        sVersion = ISNULL(sVersion, 0) + 1, sLastUpdate = GETDATE()
    WHERE sID = %s"""
                params = (s_code, x_name, x_key, x_is_hb, x_hb_intv, x_hb_ot_time, x_is_check,
                         r_plan_id, x_is_def, x_longitide, x_latitude, 
                         x_schedule_begin_time, x_schedule_end_time,
                         self.customerid)
                exec(sql, params, MYCONFIG)
            else :
                # 新增
                self.customerid = self.get_next_id()
                sql = """INSERT INTO dbo.yt_customer (
	sID, sCode, xName, xKey, xIsHB, xHBIntv, xHBOTTime, xIsCheck, sStatus, sVersion, 
	sCreateTime, sLastUpdate, rPlan_ID, xIsDef, xLongitude, xLatitude,
    xScheduleBeginTime, xScheduleEndTime
)
VALUES (
	%s, %s, %s, %s, %s, %s, %s, %s, 2, 1, 
	GETDATE(), GETDATE(), %s, %s, %s, %s,
    CAST(%s AS TIME), CAST(%s AS TIME)
)"""
                params = (self.customerid, s_code, x_name, x_key, x_is_hb, x_hb_intv, x_hb_ot_time, 
                         x_is_check, r_plan_id, x_is_def, x_longitide, x_latitude,
                         x_schedule_begin_time, x_schedule_end_time)
                exec(sql, params, MYCONFIG)
                
            # 保存用户选择
            self.save_user_selection()
                
            ## 提示保存OK
            self.editable = False
            QMessageBox.information(self, "保存成功", "数据保存成功！")
            self.load()
            # 更新用户复选框的可编辑状态
            self.update_user_editable_state()
        except Exception as e:
            QMessageBox.warning(self, "保存失败", f"保存数据时发生错误：{str(e)}")

    def save_user_selection(self):
        """
        保存用户选择
        """
        if self.customerid is None or self.customerid <= 0:
            return
            
        try:
            # 获取当前选中的用户
            selected_user_ids = set()
            for user_id, checkbox in self.user_widgets.items():
                if checkbox.isChecked():
                    selected_user_ids.add(user_id)
            
            # 获取之前选中的用户
            old_selected_ids = self.selected_users
            
            # 计算需要新增和删除的用户
            to_add = selected_user_ids - old_selected_ids
            to_delete = old_selected_ids - selected_user_ids
            
            # 删除取消选择的用户
            if to_delete:
                placeholders = ",".join(["%s"] * len(to_delete))
                delete_sql = f"DELETE FROM yt_customer_user WHERE mID = %s AND rUser_ID IN ({placeholders})"
                delete_params = [self.customerid] + list(to_delete)
                exec(sql=delete_sql, params=tuple(delete_params), config=MYCONFIG)
            
            # 添加新选择的用户
            for user_id in to_add:
                insert_sql = """INSERT INTO yt_customer_user (mID, dID, rUser_ID) 
                               VALUES (%s, %s, %s)"""
                # 获取下一个dID
                next_did = self.get_next_user_did()
                insert_params = (self.customerid, next_did, user_id)
                exec(sql=insert_sql, params=insert_params, config=MYCONFIG)
                
            # 更新已选择的用户集合
            self.selected_users = selected_user_ids
        except Exception as e:
            print(f"保存用户选择失败: {e}")
            raise e
            
    def get_next_user_did(self):
        """
        获取下一个用户明细ID
        """
        try:
            result = query(sql="SELECT ISNULL(MAX(dID), 0) + 1 AS next_did FROM yt_customer_user", config=MYCONFIG)
            if result and len(result) > 0:
                return result[0]["next_did"]
            else:
                return 1
        except Exception as e:
            print(f"获取下一个用户明细ID失败: {e}")
            return 1

    def set_default_values(self):
        """
        在新增界面中设置默认值
        """
        # 默认勾选启用心跳和启用检查
        self.xIsHBCheckBox.setChecked(True)
        self.xIsCheckCheckBox.setChecked(True)
        
        # 设置默认的心跳间隔和超时值
        self.xHBIntvLineEdit.setText("60000")
        self.xHBOTTimeLineEdit.setText("200000")
        
        # 设置默认的定时任务开始和结束时间
        self.xScheduleBeginTimeEdit.setTime(QTime.fromString("03:00:00", "HH:mm:ss"))
        self.xScheduleEndTimeEdit.setTime(QTime.fromString("05:00:00", "HH:mm:ss"))
        
        # 新增界面默认设置为可编辑
        self.editable = True
        # 更新用户复选框的可编辑状态
        self.update_user_editable_state()

    def get_next_id(self):
        """
        获取下一个可用的ID
        """
        try:
            result = query(sql="SELECT ISNULL(MAX(sID), 0) + 1 AS next_id FROM yt_customer", config=MYCONFIG)
            if result and len(result) > 0:
                return result[0]["next_id"]
            else:
                return 1
        except Exception as e:
            print(f"获取下一个ID失败: {e}")
            return 1
            
    def handle_bind(self, isDeBind : bool = False):
        if isDeBind :
            try :
                self.sCodeLineEdit.textChanged.disconnect(self.on_input_changed)
                self.xNameLineEdit.textChanged.disconnect(self.on_input_changed)
            except BaseException as e :
                pass
        else : 
            self.sCodeLineEdit.textChanged.connect(self.on_input_changed)
            self.xNameLineEdit.textChanged.connect(self.on_input_changed)
            
    def on_input_changed(self):
        self.btnSave.setEnabled(True)
        self.btnEdit.setEnabled(False)
        self.btnAudit.setEnabled(False)
        self.btnAdd.setEnabled(False)
        self.btnDel.setEnabled(False)

    def on_input_inited(self) :
        if self.customerid is not None and self.customerid > 0 :
            self.btnSave.setEnabled(False)
            self.btnEdit.setEnabled(True)
            self.btnAudit.setEnabled(True)
            self.btnAdd.setEnabled(True)
            self.btnDel.setEnabled(False)
        else :
            self.btnSave.setEnabled(True)
            self.btnEdit.setEnabled(False)
            self.btnAudit.setEnabled(False)
            self.btnAdd.setEnabled(False)
            self.btnDel.setEnabled(False)

        self.sCodeLineEdit.textChanged.connect(self.on_input_changed)
        self.xNameLineEdit.textChanged.connect(self.on_input_changed)